/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.java.editor.options;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.JCheckBox;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.parsing.api.indexing.IndexingManager;
import org.openide.util.Exceptions;

/**
 * UI for inline-hints settings (Tools-&gt;Options-&gt;Editor-&gt;Inline Hints).
 */
public class InlineHintsPanel extends javax.swing.JPanel {

    public static final String JAVA_INLINE_HINT_PARAMETER_NAME = "javaInlineHintParameterName"; //NOI18N
    public static final String JAVA_INLINE_HINT_CHAINED_TYPES = "javaInlineHintChainedTypes"; //NOI18N
    public static final String JAVA_INLINE_HINT_VAR_TYPE = "javaInlineHintVarType"; //NOI18N

    private static final Map<String, Boolean> DEFAULT_VALUES;

    static {
        Map<String, Boolean> defaultValuesBuilder = new HashMap<>();
        defaultValuesBuilder.put(JAVA_INLINE_HINT_PARAMETER_NAME, true);
        defaultValuesBuilder.put(JAVA_INLINE_HINT_CHAINED_TYPES, false);
        defaultValuesBuilder.put(JAVA_INLINE_HINT_VAR_TYPE, false);
        DEFAULT_VALUES = Collections.unmodifiableMap(defaultValuesBuilder);
    }

    private List<JCheckBox> parameterBoxes;
    private InlineHintsOptionsPanelController controller;
    private boolean changed = false;

    public InlineHintsPanel(InlineHintsOptionsPanelController controller) {
        initComponents();
        fillBoxes();
        addListeners();
    }

    public void load(InlineHintsOptionsPanelController controller) {
        this.controller = controller;

        javaInlineHintsCB.setSelected(InlineHintsSettings.isInlineHintsEnabled());

        Preferences node = InlineHintsSettings.getCurrentNode();

        for (JCheckBox box : parameterBoxes) {
            box.setSelected(node.getBoolean(box.getActionCommand(), DEFAULT_VALUES.get(box.getActionCommand())));
        }

        updateCheckBoxEnabledState(null);

        changed = false;
    }

    public void store() {

        if (javaInlineHintsCB.isSelected() != InlineHintsSettings.isInlineHintsEnabled()) {
            InlineHintsSettings.setInlineHintsEnabled(javaInlineHintsCB.isSelected());
        }

        Preferences node = InlineHintsSettings.getCurrentNode();

        for (JCheckBox box : parameterBoxes) {
            boolean value = box.isSelected();
            boolean original = node.getBoolean(box.getActionCommand(),
                    DEFAULT_VALUES.get(box.getActionCommand()));

            if (value != original) {
                node.putBoolean(box.getActionCommand(), value);
            }
        }
        try {
            node.flush();
        } catch (BackingStoreException ex) {
            Exceptions.printStackTrace(ex);
        }
        changed = false;

        EditorRegistry
                .componentList()
                .stream()
                .map(jtc -> jtc.getDocument())
                .filter(doc -> doc != null)
                .map(doc -> NbEditorUtilities.getFileObject(doc))
                .filter(fo -> fo != null)
                .forEach(fo -> IndexingManager.getDefault().refreshAllIndices(fo));
    }

    public boolean changed() {
        return changed;
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        javaInlineHintParameterNameCB = new javax.swing.JCheckBox();
        javaInlineHintChainedTypesCB = new javax.swing.JCheckBox();
        javaInlineHintVarTypeCB = new javax.swing.JCheckBox();
        javaInlineHintsCB = new javax.swing.JCheckBox();

        setBorder(javax.swing.BorderFactory.createEmptyBorder(8, 8, 8, 8));

        org.openide.awt.Mnemonics.setLocalizedText(javaInlineHintParameterNameCB, org.openide.util.NbBundle.getMessage(InlineHintsPanel.class, "InlineHintsPanel.javaInlineHintParameterNameCB.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(javaInlineHintChainedTypesCB, org.openide.util.NbBundle.getMessage(InlineHintsPanel.class, "InlineHintsPanel.javaInlineHintChainedTypesCB.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(javaInlineHintVarTypeCB, org.openide.util.NbBundle.getMessage(InlineHintsPanel.class, "InlineHintsPanel.javaInlineHintVarTypeCB.text")); // NOI18N

        org.openide.awt.Mnemonics.setLocalizedText(javaInlineHintsCB, org.openide.util.NbBundle.getMessage(InlineHintsPanel.class, "InlineHintsPanel.javaInlineHintsCB.text")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(21, 21, 21)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(javaInlineHintVarTypeCB)
                            .addComponent(javaInlineHintChainedTypesCB)
                            .addComponent(javaInlineHintParameterNameCB)))
                    .addComponent(javaInlineHintsCB))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(javaInlineHintsCB)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(javaInlineHintParameterNameCB)
                .addGap(6, 6, 6)
                .addComponent(javaInlineHintChainedTypesCB)
                .addGap(6, 6, 6)
                .addComponent(javaInlineHintVarTypeCB)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void updateCheckBoxEnabledState(ActionEvent evt) {
        if (javaInlineHintsCB.isSelected() && parameterBoxes.stream().noneMatch(JCheckBox::isSelected)) {
            if (evt != null && evt.getSource() == javaInlineHintsCB) {
                // restore default if hints are toggled on and no other parameter boxes are selected
                // this ensures that the view aciton won't become a no-op
                for (JCheckBox box : parameterBoxes) {
                    box.setSelected(DEFAULT_VALUES.get(box.getActionCommand()));
                }
            } else {
                // disable inline hints if none of the parameter boxes are selected
                javaInlineHintsCB.setSelected(false);
            }
        }
        // enable parameter boxes only if inline hints are active
        parameterBoxes.forEach(box -> box.setEnabled(javaInlineHintsCB.isSelected()));
    }


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JCheckBox javaInlineHintChainedTypesCB;
    private javax.swing.JCheckBox javaInlineHintParameterNameCB;
    private javax.swing.JCheckBox javaInlineHintVarTypeCB;
    private javax.swing.JCheckBox javaInlineHintsCB;
    // End of variables declaration//GEN-END:variables

    private void fillBoxes() {
        parameterBoxes = new ArrayList<>();
        parameterBoxes.add(javaInlineHintParameterNameCB);
        parameterBoxes.add(javaInlineHintChainedTypesCB);
        parameterBoxes.add(javaInlineHintVarTypeCB);

        javaInlineHintParameterNameCB.setActionCommand(JAVA_INLINE_HINT_PARAMETER_NAME);
        javaInlineHintChainedTypesCB.setActionCommand(JAVA_INLINE_HINT_CHAINED_TYPES);
        javaInlineHintVarTypeCB.setActionCommand(JAVA_INLINE_HINT_VAR_TYPE);
    }

    private void addListeners() {
        ActionListener al = e -> checkBoxChanged(e);
        javaInlineHintsCB.addActionListener(al);
        for (JCheckBox box : parameterBoxes) {
            box.addActionListener(al);
        }
    }

    private void checkBoxChanged(ActionEvent evt) {
        updateCheckBoxEnabledState(evt);
        if (javaInlineHintsCB.isSelected() != InlineHintsSettings.isInlineHintsEnabled()) {
            changed = true;
            return;
        }        
        Preferences node = InlineHintsSettings.getCurrentNode();
        for (JCheckBox box : parameterBoxes) {
            if (node.getBoolean(box.getActionCommand(), DEFAULT_VALUES.get(box.getActionCommand())) != box.isSelected()) {
                changed = true;
                return;
            }
        }
        changed = false;
    }

}
